"""公告板/收集员额职能"""
import os
import re
from datetime import datetime, timedelta

import bleach
from typing import List
from fastapi import Request
from fastapi.templating import Jinja2Templates
from sqlalchemy import and_, asc, desc, func, insert, or_, select
from sqlalchemy.sql.expression import Select
from sqlalchemy.orm import Session

from core.database import DBConnect
from core.exception import AlertException
from core.models import Board, BoardNew, Member, WriteBaseModel
from core.template import TemplateService, UserTemplates
from lib.common import (
    FileCache, StringEncrypt, cut_name, dynamic_create_write_table, get_admin_email,
    get_admin_email_name, get_editor_image, thumbnail
)
from lib.mail import mailer
from lib.member import MemberDetails
from service.board_file_service import BoardFileService as FileService


class BoardConfig():
    """公告栏设置的信息"""

    def __init__(self, request: Request, board: Board) -> None:
        self.board = board
        self.config = request.state.config
        self.is_mobile = request.state.is_mobile
        self.request = request

        self.member = MemberDetails(request, request.state.login_member)
        self.member.admin_type = self.member.get_admin_type(board=board)

    @property
    def gallery_width(self) -> int:
        """返回库列表图像的水平尺寸。

        Returns:
            int: 街道大小的画廊图像.
        """
        return (self.board.bo_mobile_gallery_width if self.is_mobile else self.board.bo_gallery_width) or 200

    @property
    def gallery_height(self) -> int:
        """图库列表返回图像纵向大小.

        Returns:
            int: 图库图像垂直尺寸.
        """
        return (self.board.bo_mobile_gallery_height if self.is_mobile else self.board.bo_gallery_height) or 150

    @property
    def image_width(self) -> int:
        """返回要在公告板详细信息页面上显示的图像大小.

        Returns:
            int: 道路大小的图像.
        """
        return self.board.bo_image_width or None

    @property
    def page_rows(self) -> int:
        """返回每页要打印的行数。

        Returns:
            int: 每个公告板页面要输出的行数.
        """
        # 检查你是否移动
        bo_page_rows = self.board.bo_mobile_page_rows if self.is_mobile else self.board.bo_page_rows
        page_rows = self.config.cf_mobile_page_rows if self.is_mobile else self.config.cf_page_rows

        return bo_page_rows if bo_page_rows != 0 else page_rows

    @property
    def table_width(self) -> int:
        """返回公告栏表格的水平尺寸。

        Returns:
            int: 公告板的十字尺寸表:.
        """
        return self.board.bo_table_width or 100

    @property
    def get_table_width(self) -> str:
        """返回公告板表的十字尺寸和单位.

        Returns:
            str: 公告板的十字尺寸表:.
        """
        unit = "px" if self.table_width > 100 else "%"

        return f"{self.table_width}{unit}"

    @property
    def select_editor(self) -> str:
        """返回要在公告栏中使用的编辑器。

        Returns:
            str: 什么用于公告板.
        """
        if not self.board.bo_use_dhtml_editor or not self.config.cf_editor:
            return "textarea"

        return self.board.bo_select_editor or self.config.cf_editor

    @property
    def subject(self) -> str:
        """返还公告栏标题。

        Returns:
            str: 公告板标题.
        """
        if self.request.state.is_mobile and self.board.bo_mobile_subject:
            return self.board.bo_mobile_subject
        else:
            return self.board.bo_subject

    @property
    def use_captcha(self) -> bool:
        """在留言板上返回是否使用截图。

        Returns:
            bool: 是否在公告板上使用captas.
        """
        if self.member.admin_type:
            return False

        if not self.member or self.board.bo_use_captcha:
            return True

        return False

    @property
    def use_email(self) -> bool:
        """在公告栏上返回是否使用电子邮件。

        Returns:
            bool: 在公告板上使用电子邮件.
        """
        return self.config.cf_email_use and self.board.bo_use_email

    @property
    def write_min(self) -> int:
        """“限制帖子注册的最小页数”"""
        return self._get_write_text_limit(self.board.bo_write_min)

    @property
    def write_max(self) -> int:
        """邮政登记的最高限额"""
        return self._get_write_text_limit(self.board.bo_write_max)

    def cut_write_subject(self, subject, cut_length: int = 0) -> str:
        """根据给定的cut_length剪裁subject字符串，必要时添加“…”。

        Args:
            - subject: 要剪切的主题字符串.
            - cut_length: subject 字符串的最大长度. Default: 0

        Returns:
            - str : 已修改 subject 字符串.
        """
        cut_length = cut_length or (self.board.bo_mobile_subject_len if self.is_mobile else self.board.bo_subject_len)

        if not cut_length:
            return subject

        return subject[:cut_length] + "..." if len(subject) > cut_length else subject

    def get_category_list(self) -> list:
        """返回公告板类别的列表.

        Returns:
            list: 公告板类别列表.
        """
        if (not self.board.bo_use_category
                or self.board.bo_category_list == ""):
            return []

        return self.board.bo_category_list.split("|")

    def get_display_ip(self, ip: str) -> str:
        """IP 将地址转换为显示格式
        Args:
            ip (str): IP 地址
        """
        if self.member.admin_type:
            return ip

        if self.board.bo_use_ip_view:
            return re.sub(r"([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)", "\\1.#.#.\\4", ip)
        else:
            return ""

    def get_member_signature(self, mb_id: str = None) -> str:
        """如果使用公告板上的签名视图，请返回成员的签名。.

        Args:
            mb_id (str): 会员Id. Defaults to None.

        Returns:
            str: 成员签名
        """
        try:
            db = DBConnect().sessionLocal()

            if self.board.bo_use_signature and mb_id:
                member = db.scalar(
                    select(Member).filter(Member.mb_id == mb_id))

                return getattr(member, "mb_signature", "")
            else:
                return ""
        finally:
            db.close()

    def get_notice_list(self) -> list:
        """返回公告栏公告文章编号列表。

        Returns:
            list:.公告栏公告文章编号列表。
        """
        if not self.board.bo_notice:
            return []
        return self.board.bo_notice.split(",")

    def get_list_sort_query(self, model: WriteBaseModel, query: Select) -> Select:
        """返回包含帖子列表排序的query。

        Args:
            query (Select): 查询帖子列表

        Returns:
         Select:查询帖子列表
        """
        if self.board.bo_sort_field:
            sort_fields = self.board.bo_sort_field.split(",")
            for field in sort_fields:
                field_parts = field.strip().split(" ")
                sort_field = getattr(model, field_parts[0])
                if not sort_field:
                    continue
                sort_order = asc(sort_field) if len(field_parts) == 1 or field_parts[1].lower() == "asc" else desc(sort_field)
                query = query.order_by(sort_order)
        else:
            query = query.order_by(model.wr_num, model.wr_reply)

        return query

    def is_list_level(self) -> bool:
        """检查查看帖子列表的权限."""
        return self._can_action_by_level(self.board.bo_list_level)

    def is_read_level(self) -> bool:
        """检查阅读帖子的权限."""
        return self._can_action_by_level(self.board.bo_read_level)

    def is_write_level(self) -> bool:
        """验证公告栏管理员"""
        return self._can_action_by_level(self.board.bo_write_level)

    def is_reply_level(self) -> bool:
        """检查您的权限以回答帖子."""
        return self._can_action_by_level(self.board.bo_reply_level)

    def is_comment_level(self) -> bool:
        """检查写评论的权限."""
        return self._can_action_by_level(self.board.bo_comment_level)

    def is_link_level(self) -> bool:
        """当写一篇文章, 检查添加链接的权限."""
        return self._can_action_by_level(self.board.bo_link_level)

    def is_upload_level(self) -> bool:
        """当写一篇文章, 检查上传文件的权限."""
        return self._can_action_by_level(self.board.bo_upload_level)

    def is_download_level(self) -> bool:
        """检查下载帖子附件的权限."""
        return self._can_action_by_level(self.board.bo_download_level)

    def is_html_level(self) -> bool:
        """当写一篇文章, HTML检查要添加的权限."""
        return self._can_action_by_level(self.board.bo_html_level)

    def is_icon_hot(self, hit: int) -> bool:
        """返回是否热门文章图标输出.

        Args:
            hit (int): 点击数.

        Returns:
            bool: 是否输出热门文章图标.
        """
        return hit >= self.board.bo_hot if self.board.bo_hot > 0 else False

    def is_icon_new(self, reg_date: datetime) -> bool:
        """新文章返回是否输出图标.

        Args:
            reg_date (str): 注册日.

        Returns:
            bool: 是否输出新文章图标.
        """
        result = False
        if self.board.bo_new > 0:
            result = reg_date > (datetime.now() - timedelta(hours=int(self.board.bo_new)))

        return result

    def is_board_notice(self, wr_id: int) -> bool:
        """检查帖子是否为消息。.

        Args:
            wr_id (int): 帖子名称

        Returns:
            bool: 球是否在打响
        """
        return str(wr_id) in self.board.bo_notice.split(",")

    def is_read_point(self, write: WriteBaseModel) -> bool:
        """读取点检查"""
        """글 읽기 포인트 체크"""
        return self._can_action_by_point(self.board.bo_read_point, write)

    def is_write_point(self) -> bool:
        """书写点检查"""
        return self._can_action_by_point(self.board.bo_write_point)

    def is_comment_point(self) -> bool:
        """检查留言点"""
        return self._can_action_by_point(self.board.bo_comment_point)

    def is_download_point(self, write: WriteBaseModel) -> bool:
        """下载点检查"""
        return self._can_action_by_point(self.board.bo_download_point, write)

    def is_modify_by_comment(self, wr_id: int) -> bool:
        """可以根据评论的数量来固定发布"""
        return self._can_action_by_comment_count(wr_id, self.board.bo_count_modify)

    def is_delete_by_comment(self, wr_id: int) -> bool:
        """你可以根据评论的数量删除帖子吗"""
        return self._can_action_by_comment_count(wr_id, self.board.bo_count_delete)

    def set_board_notice(self, wr_id: int, insert: bool = False) -> str:
        """新文章

        Args:
            wr_id (int): _description_
            insert (bool, optional): _description_. Defaults to False.

        Returns:
            str: _description_
        """
        notice_ids = self.board.bo_notice.split(",") if self.board.bo_notice else []
        exist = self.is_board_notice(wr_id)

        if insert and not exist:
            notice_ids.append(str(wr_id))
        elif not insert and exist:
            notice_ids.remove(str(wr_id))

        return ",".join(map(str, notice_ids))

    def set_wr_name(self, member: Member = None, default_name: str = None) -> str:
        """确认是否使用实名后，如果是实名，将返还姓名或昵称。

        Args:
            board (Board): 公告板 object
            member (Member): 成员 object 

        Returns:
            str: 姓名或昵称
        """
        if member:
            if self.board.bo_use_name:
                return member.mb_name
            return member.mb_nick
        elif default_name:
            return default_name
        else:
            raise AlertException("登录会话的过期时间, 非会员写作是一种不典型的方法，如作者的名字没有列出.", 400)

    def _can_action_by_level(self, level: int) -> bool:
        """确认您有权撰写帖子。

        Args:
            level (int): 权限级别

        Returns:
            bool: 是否可能采取行动
        """
        if self.member.admin_type:
            return True
        return level <= self.member.mb_level

    def _can_action_by_comment_count(self, wr_id: int, limit: int) -> bool:
        """注释的数量决定了是否可以采取行动.

        Args:
            request (Request): Request 对象
            wr_id (int): 帖子名称
            limit (int): 要限制的评论数

        Returns:
            bool: 是否可以纠正
        """
        if self.member.admin_type:
            return True

        with DBConnect().sessionLocal() as db:
            write_model = dynamic_create_write_table(self.board.bo_table)
            comment_count = db.scalar(
                select(func.count())
                .select_from(write_model)
                .where(
                    write_model.wr_parent == wr_id,
                    write_model.wr_is_comment == 1
                )
            )

        if limit and limit <= comment_count:
            return False
        return True

    def _can_action_by_point(self, point: int, write: WriteBaseModel = None) -> bool:
# “根据积分判断是否可以行动。

# Args:
# point（int）：要增加的点

# Returns:
# bool：是否可以行动
# """
        member_point = getattr(self.member, "mb_point", 0)

        # 经理 or 重点是 0 如果在上面，则通过
        if self.member.admin_type or point >= 0:
            return True
        #如果帖子创建者or帖子创建者IP和当前连接IP相同，则通过
        if write:
            if (is_owner(write, self.member.mb_id)
                or (not self.member.mb_id
                    and self.board.bo_read_level == 1
                    and write.wr_ip == self.request.client.host)):
                return True

        return (member_point + point) >= 0

    def _get_write_text_limit(self, limit: int) -> int:
        """post/评论写作限制返回韩语.

        Args:
            limit (int): 用韩语写的帖子数量.

        Returns:
            int: 用韩语写的帖子数量.
        """
        if self.member.admin_type or self.board.bo_use_dhtml_editor:
            return 0
        return limit


def write_search_filter(
        model: WriteBaseModel,
        category: str = None,
        search_field: str = None,
        keyword: str = None,
        operator: str = "or") -> Select:
    """应用公告栏搜索过滤器。
-功能与Gnuboard 5中的get_sql_search相同。

    Args:
        model (WriteBaseModel): 要搜索的模型(post).
        category (str, optional): 要搜索的分类. Defaults to None.
        fields (str, optional): 要搜索的字段. Defaults to None.
        keyword (str, optional): 要搜索的字符串. Defaults to None.
        operator (str, optional): 搜索条件. Defaults to None.

    Returns:
        Select: 应用筛选器的查询。
    """
    with DBConnect().sessionLocal() as db:
        fields = []
        is_comment = False

        query = select()
        # 分类
        if category:
            query = query.where(model.ca_name == category)

#设置搜索字段和单词
#将搜索词分离为单词，根据operator生成过滤器
        word_filters = []
        words = keyword.split(" ")
        if search_field:
            # search_field以{字段名}、{评论与否}的格式传递（0：评论，1：帖子）
            tmp = search_field.split(",")
            fields = tmp[0].split("||")
            is_comment = (tmp[1] == "0") if len(tmp) > 1 else False

            # 删除密码字段
            if "wr_password" in fields:
                fields.remove("wr_password")

            # 创建字段搜索筛选器（or条件）
            for word in words:
                if not word.strip():
                    continue
                word_filters.append(or_(
                    *[getattr(model, field).like(f"%{word}%") for field in fields if hasattr(model, field)]))

        # 将or或and应用于孤立词搜索过滤器
        if operator == "and":
            query = query.where(and_(*word_filters))
        else:
            query = query.where(or_(*word_filters))

        # 搜索评论
        if is_comment:
            query = query.where(model.wr_is_comment == 1)
            # 因为只需要查询一篇文章，所以需要获取wr_parent列表并按in条件重新过滤
            parents = db.scalars(query.add_columns(model)).all()
            query = select().where(model.wr_id.in_([row.wr_parent for row in parents]))

        return query


def get_next_num(bo_table: str) -> int:
    """
    获得公告栏的下一个文章编号。
    """
    try:
        db = DBConnect().sessionLocal()

        write_model = dynamic_create_write_table(bo_table)
        min_wr_num = db.scalar(select(func.coalesce(func.min(write_model.wr_num), 0)))

        return min_wr_num - 1
    finally:
        db.close()


def get_list(request: Request, db: Session, write: WriteBaseModel, board_config: BoardConfig, subject_len: int = 0):
    """为帖子列表的输出添加所需信息。
-功能与Gnuboard 5中的get_list相同。

    Args:
        request (Request): FastAPI Request 对象.
        write (WriteBaseModel): post对象.
        board (Board): 公告栏对象.
        subject_len (int, optional): 帖子标题长度. Defaults to 0.

    Returns:
        WriteBaseModel: 帖子列表.
    """
    file_service = FileService(request, db)
    write.subject = board_config.cut_write_subject(write.wr_subject, subject_len)
    write.name = cut_name(request, write.wr_name)
    write.email = StringEncrypt().encrypt(write.wr_email)
    write.datetime = write.wr_datetime.strftime("%y-%m-%d")

    write.is_notice = board_config.is_board_notice(write.wr_id)
    write.icon_secret = "secret" in write.wr_option
    write.icon_hot = board_config.is_icon_hot(write.wr_hit)
    write.icon_new = board_config.is_icon_new(write.wr_datetime)
    write.icon_file = file_service.is_exist(board_config.board.bo_table, write.wr_id)
    write.icon_link = write.wr_link1 or write.wr_link2
    write.icon_reply = write.wr_reply

    return write


#FIXME：如果在有回帖的情况下更改bo_reply_order，则无法输入错误
#ex）最初输入正向ABC，如果反向更改，last_reply_char为A（Min）。
#反向char_end为A，A-1为异常处理，不输入回帖
def generate_reply_character(board: Board, write):
    """生成反评论步骤字符串
    Args:
        board：公告板object
        write：留言/回复帖子object
    Raises:
        AlertException: Z处理超出范围的字符串异常
    Returns:
        str:A~Z的连续字符串（Ex:A，B，AA，AB，ABA…）
    """
    db = DBConnect().sessionLocal()
    write_model = dynamic_create_write_table(board.bo_table)

    #剪切最后一个字符串
    if not write.wr_is_comment:
        origin_reply = write.wr_reply
        query = (
            select(func.substr(write_model.wr_reply, -1).label("reply"))
            .where(
                write_model.wr_num == write.wr_num,
                func.char_length(write_model.wr_reply) == (len(origin_reply) + 1)
            )
        )
        if origin_reply:
            query = query.where(write_model.wr_reply.like(f"{origin_reply}%"))
    else:
        origin_reply = write.wr_comment_reply
        query = (
            select(func.substr(write_model.wr_comment_reply, -1).label("reply"))
            .where(
                write_model.wr_parent == write.wr_parent,
                write_model.wr_comment == write.wr_comment,
                func.char_length(write_model.wr_comment_reply) == (len(origin_reply) + 1)
            )
        )
        if origin_reply:
            query = query.where(write_model.wr_comment_reply.like(f"{origin_reply}%"))

    # 正向最大值，反向最小值
    if board.bo_reply_order:
        last_reply_char = db.scalar(query.order_by(desc("reply")))
        char_begin = "A"
        char_end = "Z"
        char_increase = 1
    else:
        last_reply_char = db.scalar(query.order_by(asc("reply")))
        char_begin = "Z"
        char_end = "A"
        char_increase = -1

    if last_reply_char == char_end:  # A~Z银色 26 就这样..
        raise AlertException("没有更多的答案。. 答案是 26只有狗是可能的.")

    if not last_reply_char:
        reply_char = char_begin
    else:
        reply_char = chr(ord(last_reply_char) + char_increase)

    db.close()

    return origin_reply + reply_char


def is_owner(mb_id_object: object, mb_id: str = None):
    """ post/检查你是否是评论者.

    Args:
        mb_id_object (object): mb_id 具有属性的对象
        mb_id (str, optional): 会员Id. Defaults to None.

    Returns:
        _type_: _description_
    """
    attr_mb_id = getattr(mb_id_object, "mb_id", None)
    if attr_mb_id:
        return attr_mb_id == mb_id
    else:
        return False


def send_write_mail(request: Request, board: Board, write: WriteBaseModel, origin_write: WriteBaseModel = None):
    """留言/回复/回帖时，发送邮件。

    Args:
        request：request对象
        board：公告板object
        write（WriteBaseModel）：撰写的帖子/回复/评论object 
        origin_write（WriteBaseModel，optional）：原始帖子/回复object。Defaults to None.  parent_write
    """
    with DBConnect().sessionLocal() as db:
        config = request.state.config
        templates = Jinja2Templates(
                directory=TemplateService.get_templates_dir())

        def _add_admin_email(admin_id: str):
            admin = db.scalar(select(Member).filter_by(mb_id=admin_id))
            if admin:
                send_email_list.append(admin.mb_email)

        send_email_list = []
        if config.cf_email_wr_board_admin and board.bo_admin:
            _add_admin_email(board.bo_admin)
        if config.cf_email_wr_group_admin and board.group.gr_admin:
            _add_admin_email(board.group.gr_admin)
        if config.cf_email_wr_super_admin:
            _add_admin_email(config.cf_admin)
        if config.cf_email_wr_write and origin_write:
            send_email_list.append(origin_write.wr_email)

        if write.wr_is_comment:
            act = "评论"
            link_url = str(request.url_for("read_post", bo_table=board.bo_table, wr_id=origin_write.wr_id)) + f"#c_{write.wr_id}"

            if config.cf_email_wr_comment_all:
                #  向所有写评论的人发送邮件
                write_model = dynamic_create_write_table(board.bo_table)
                query = select(write_model.wr_email).distinct().where(
                    write_model.wr_email.notin_(["", write.wr_email]),
                    write_model.wr_parent == origin_write.wr_id
                )
                comments = db.scalars(query).all()
                send_email_list.extend(email for email in comments)
        else:
            act = "回复文章" if origin_write else "新文章"
            link_url = request.url_for("read_post", bo_table=board.bo_table, wr_id=write.wr_id)

        #删除重复电子邮件
        send_email_list = list(set(send_email_list))
        for email in send_email_list:
            subject = f"[{config.cf_title}] {board.bo_subject} 在公告板上 {act}这个已经注册了."
            body = templates.TemplateResponse(
                "bbs/mail_form/write_update_mail.html", {
                    "request": request,
                    "act": act,
                    "board": board,
                    "wr_subject": write.wr_subject,
                    "wr_name": write.wr_name,
                    "wr_content": write.wr_content,
                    "link_url": link_url,
                }
            ).body.decode("utf-8")
            mailer(get_admin_email(request), email, subject, body, get_admin_email_name(request))


def get_list_thumbnail(request: Request, board: Board, write: WriteBaseModel, thumb_width: int, thumb_height: int, **kwargs):
    """生成帖子列表的缩略图图像。

    Args:
        request (Request): _description_
        board (Board): _description_
        write (WriteBaseModel): _description_
        thumb_width (int, optional): _description_. Defaults to 0.
        thumb_height (int, optional): _description_. Defaults to 0.
    """
    config = request.state.config
    with DBConnect().sessionLocal() as db:
        service = FileService(request, db)
        images, files = service.get_board_files_by_type(board.bo_table, write.wr_id)
    source_file = None
    result = {"src": "", "alt": "", "noimg":""}

    if images:
        # TODO : 从缓存的数据中查看帖子的文件信息.
        # 上传文件列表
        # 업로드 파일 목록
        source_file = images[0].bf_file
        result["alt"] = images[0].bf_content
    else:
        # TODO : 从缓存的数据中查找帖子的文本信息.
        # 帖子文本
        editor_images = get_editor_image(write.wr_content, view=False)
        for image in editor_images:
            try:
                ext = image.split(".")[-1].lower()

#作为编辑器插入的图像的地址是Web路径，因此将路径更改为os.path进行检查。
#如果希望外部图像也显示为缩略图，则必须修改缩略图条件和生成逻辑。
                image = "./data/editor/" + image.split("/data/editor/")[1]

                # 检查图像路径中的文件是否存在，是否为图像文件
                if (os.path.exists(image)
                        and os.path.isfile(image)
                        and os.path.getsize(image) > 0
                        and ext in config.cf_image_extension):
                    source_file = image
                    break

            except Exception as e:
                print(e)
                continue

    # 创建缩略图
    if source_file:
        result["src"] = thumbnail(source_file, width=thumb_width, height=thumb_height, **kwargs)
    # 当没有图像时
    else:
        result["src"] = thumbnail("./static/img/dummy-donotremove.png",
                        target_path="./data/thumbnail_tmp",
                        width=thumb_width, height=thumb_height, **kwargs)
        result["noimg"] = "img_not_found"

    return result


# 关于文本中的图像标记 width过滤功能强制
def set_image_width(content: str, width: str = None) -> str:
    """关于文本中的图像标记 width过滤功能强制

    Args:
        content (str): 帖子文本
        width (int, optional): 图像 width. Defaults to 0.

    Returns:
        str: 在图像标签上 width添加的文本
    """
    if width:
        content = re.sub(r"<img([^>]+)>", f"<img\\1 width={width}>", content)
    return content


def is_secret_write(write: WriteBaseModel = None) -> bool:
    """检查它是否是一个秘密.

    Args:
        write (WriteBaseModel, optional): post object. Defaults to None.

    Returns:
        bool: 秘密写作与否
    """
    return "secret" in getattr(write, "wr_option", "")


def url_auto_link(text: str, request: Request, is_nofollow: bool = True) -> str:
    """包含在字符串中 URL转换为链接.

    Args:
        text (str): 要转换的字符串.
        request (Request): Request 对象.
        is_nofollow: nofollow 是否包含属性. Defaults to True.

    Returns:
        str: 已转换字符串.
    """
    cf_link_target = getattr(request.state.config, "cf_link_target", "_blank")

    def _nofollow(attrs, _):
        if is_nofollow:
            return bleach.callbacks.nofollow(attrs)
        else:
            return attrs

    def _target(attrs, _):
        attrs = bleach.callbacks.target_blank(attrs)
        if (None, "target") in attrs:
            attrs[(None, "target")] = cf_link_target
        return attrs

    return bleach.linkify(text, callbacks=[_nofollow, _target], parse_email=True)


def is_write_delay(request: Request) -> bool:
    """检查是否可以在特定时间间隔内重新撰写文章的函数“"""
    if request.state.is_super_admin:
        return True

    delay_sec = int(request.state.config.cf_delay_sec)
    current_time = datetime.now()
    write_time = request.session.get("ss_write_time")

    if delay_sec > 0:
        time_interval = timedelta(seconds=delay_sec)
        if write_time:
            available_time = datetime.strptime(write_time, "%Y-%m-%d %H:%M:%S") + time_interval
            if available_time > current_time:
                return False

    return True


def set_write_delay(request: Request):
    """将文章撰写时间保存到会话的函数“"""
    delay_sec = int(request.state.config.cf_delay_sec)

    if not request.state.is_super_admin and delay_sec > 0:
        request.session["ss_write_time"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")


def insert_board_new(bo_table: str, write: WriteBaseModel) -> None:
    """最新文章表注册函数

    Args:
        bo_table (str): 公告板代码
        write (WriteBaseModel): post模型
    """
    db = DBConnect().sessionLocal()
    db.execute(
        insert(BoardNew)
        .values(
            bo_table=bo_table,
            wr_id=write.wr_id,
            wr_parent=write.wr_parent,
            mb_id=write.mb_id,
        )
    )
    db.commit()
    db.close()


def get_bo_table_list(added_bo_table_list: List[str] = None) -> list:
    from install.default_values import default_boards

    bo_table_list = [board_value_dict["bo_table"] for board_value_dict in default_boards]
    if added_bo_table_list:
        bo_table_list.extend(added_bo_table_list)
    return bo_table_list


def render_latest_posts(request: Request, skin_name: str = 'basic', bo_table: str='',
                        rows: int = 10, subject_len: int = 40):
    """这个函数都是在前端掉用的，用于从数据库中取出数据，然后渲染；并生成缓存，如果已经有缓存，就直接返回；
    最新文章列表HTML输出 
    Args:
        request (Request): _description_
        skin_name（str，optional）：皮肤路径。Defaults to ''.
        bo_table（str，optional）：公告栏代码。Defaults to ''.
        rows（int，optional）：曝光帖子数。Defaults to 10.
        subject_len（int，optional）：标题长度限制。Defaults to 40.
    Returns:
        str: 最新文章HTML
    """
    templates = UserTemplates()
    templates.env.globals["get_list_thumbnail"] = get_list_thumbnail

    device = request.state.device
    file_cache = FileCache()
    cache_filename = f"latest-{bo_table}-{device}-{skin_name}-{rows}-{subject_len}-{file_cache.get_cache_secret_key()}.html"
    cache_file = os.path.join(file_cache.cache_dir, cache_filename)

    # 如果有缓存的文件，则读取并返回文件
    if os.path.exists(cache_file):
        return file_cache.get(cache_file)

    with DBConnect().sessionLocal() as db:
       #公告栏设置
        board = db.get(Board, bo_table)
        if not board:
            return ""

        board_config = BoardConfig(request, board)
        board.subject = board_config.subject

       #查看帖子列表
        write_model = dynamic_create_write_table(bo_table)
        writes = db.scalars(
            select(write_model)
            .where(write_model.wr_is_comment == 0)
            .order_by(write_model.wr_num)
            .limit(rows)
        ).all()
        for write in writes:
            write = get_list(request, db, write, board_config, subject_len)

    context = {
        "request": request,
        "board": board,
        "writes": writes,
        "bo_table": bo_table,
    }
    temp = templates.TemplateResponse(f"latest/{skin_name}.html", context)
    temp_decode = temp.body.decode("utf-8")

  #创建缓存文件
    file_cache.create(temp_decode, cache_file)

    return temp_decode
